import copy
import re

import jsonpath
import yaml
from requests import Response

from commons.debug_talk import DebugTalk
from commons.yaml_util import append_yaml


class ExtractUtil:

    @staticmethod
    def hot_load_replace(yaml_str):
        """
        解析数据提取表达式，并将提取到的数据替换掉表达式，完成 yaml 取值
        :param yaml_str: 需要通过表达式取值的 yaml 字符串
        :return: 返回取值后替换掉表达式的 yaml 字符串
        """
        # 用来匹配 提取规则 的正则表达式
        regexp = r"\$\{(.*?)\((.*?)\)\}"
        # 通过正则表达式匹配所有 提取规则，得到所有函数名和参数的列表
        fun_list = re.findall(regexp, yaml_str)
        if fun_list:
            # 循环,f[0]就是函数名，f[1]参数名
            for fun in fun_list:
                if fun[1] == "":  # 没有参数
                    # 通过反射的方式获取 DebugTalk 中对应的函数句柄，并对其进行调用
                    new_value = getattr(DebugTalk, fun[0])()
                else:  # 有参数，可以是1个或者N个
                    # 通过反射的方式获取 DebugTalk 中对应的函数句柄，并对其进行调用
                    new_value = getattr(DebugTalk, fun[0])(*fun[1].split(','))
                # 判断如果是全数字类型字符串，那么加上单引号
                # isinstance 判断实例类型，isdigit 判断字符串是否为全数字字符串
                if isinstance(new_value, str) and new_value.isdigit():
                    new_value = f"'{new_value}'"
                # 拼接完整表达式
                old_value = "${" + fun[0] + "(" + fun[1] + ")}"
                # 将提取到的值，替换掉 yaml 表达式
                yaml_str = yaml_str.replace(old_value, str(new_value))
        return yaml_str

    @staticmethod
    def change(data_dict: dict):
        """
        解析字典中的数据提取表达式，将提取的值替换表达式
        :param data_dict: 需要解析的字典
        :return: 返回解析后的字典
        """
        # 将字典转化为 yaml 字符串
        yaml_str = yaml.dump(data_dict)
        # 通过 hot_load_replace 取得 表达式需要的数据
        new_yaml_str = ExtractUtil.hot_load_replace(yaml_str)
        # 将 yaml 字符串转化为字典，并返回
        data_dict = yaml.safe_load(new_yaml_str)
        return data_dict

    @staticmethod
    def find_expr(response: Response, attr_name: str, expr: str, index: int):
        """
        通过 yaml 表达式提取 response 中对应字段的值
        :param response: 请求返回的 response
        :param attr_name: 被提取的数据的字段名称
        :param expr: 提取数据的表达式，可以是json，也可以是正则
        :param index: 需要提取的数据的下标
        :return: 返回需要提取的值
        """
        # 深拷贝一份 response 的值
        resp = copy.deepcopy(response)
        # 把json()改成一个属性
        try:
            resp.json = resp.json()
        except:
            resp.json = {"msg": "response is not json"}
        # 如果表达式为空 则直接获取属性值
        if expr is None:
            # 判断是否包含该属性
            if hasattr(resp, attr_name):
                # 获取属性值
                var_value = getattr(resp, attr_name)
            else:
                var_value = "not find attr_name"
        else:
            data = getattr(resp, attr_name)
            # 判断提取方式
            # 正则提取：（只能提取字符串）
            # re.findall
            # JSONPATH提取：（只能提取字典格式）
            # jsonpath.jsonpath()
            if expr.startswith("$"):
                # jsonpath 提取
                lis = jsonpath.jsonpath(data, expr)
            else:
                # 如果不是 json ，通过正则表达式提取值
                lis = re.findall(expr, data)
            # 通过下标取值
            if lis:
                var_value = lis[index]
            else:
                var_value = "not find_expr data"
        return var_value

    @staticmethod
    def extract(response: Response, var_name: str, attr_name: str, expr: str, index: int):
        """
        根据 yaml extract 表达式提取 response 对应字段数据，并写入到 extract.yaml 中
        :param response: 请求返回的 response
        :param var_name: response 中需要提取数据的主体，比如 headers/text
        :param attr_name: 被提取的数据的字段名称
        :param expr: 提取数据的表达式，可以是json，也可以是正则
        :param index: 需要提取的数据的下标
        """
        # 通过表达式提取对应的数据
        var_value = ExtractUtil.find_expr(response, attr_name, expr, index)
        # 把提取的数据写入 extract.yaml
        path = "./testcases/extract.yaml"
        append_yaml(path, {var_name: var_value})
